use std::sync::Arc;

use thiserror::Error;
use tokio::sync::Mutex;
use tracing::warn;

use bc_base::{
    BlockHeight, BlockId, TransactionId,
    block::BlockIndexNode,
    transaction::{Transaction, TransactionError, TransactionIndexNode},
};
use bc_lib::top_storage::{
    BlockAndIndexStorageWithCache, MiscItemStorageWithCache, TopStorageError,
    TransactionAndIndexStorageWithCache,
};

#[derive(Error, Debug, PartialEq)]
pub enum TransactionIndexError {
    #[error("TopStorageError: {0}")]
    TopStorageError(#[from] TopStorageError),

    #[error("TransactionError: {0}")]
    TransactionError(#[from] TransactionError),

    // there is no block id in tx_index
    #[error("not found block id in tx_index({0})")]
    NotFoundBlockIdInTxIndex(TransactionId),

    // there is block index in db
    #[error("not found block index in db({0})")]
    NotFoundBlockIndexInDb(BlockId),

    // there is no tx index in db
    #[error("not found tx index in db({0})")]
    NotFoundTxIndexInDb(TransactionId),
}

#[derive(Debug)]
pub(super) struct TransactionAndIndexManager {
    block_and_index_storage_with_cache: Arc<Mutex<BlockAndIndexStorageWithCache>>,
    tx_and_index_storage_with_cache: Arc<Mutex<TransactionAndIndexStorageWithCache>>,
    misc_item_storage_with_cache: Arc<Mutex<MiscItemStorageWithCache>>,
}

impl TransactionAndIndexManager {
    pub async fn new(
        block_and_index_storage_with_cache: Arc<Mutex<BlockAndIndexStorageWithCache>>,
        tx_and_index_storage_with_cache: Arc<Mutex<TransactionAndIndexStorageWithCache>>,
        misc_item_storage_with_cache: Arc<Mutex<MiscItemStorageWithCache>>,
    ) -> Self {
        Self {
            block_and_index_storage_with_cache,
            tx_and_index_storage_with_cache,
            misc_item_storage_with_cache,
        }
    }
}

// write
impl TransactionAndIndexManager {
    pub async fn update_tx_index_node_sync(
        &mut self,
        tx_id: &TransactionId,
        tx_ind: TransactionIndexNode,
    ) -> Result<(), TransactionIndexError> {
        self.tx_and_index_storage_with_cache
            .lock()
            .await
            .update_tx_index_node_sync(tx_id, tx_ind)
            .await?;
        Ok(())
    }
}

// read
impl TransactionAndIndexManager {
    pub async fn tx_exists(&self, tx_id: &TransactionId) -> bool {
        self.tx_and_index_storage_with_cache
            .lock()
            .await
            .tx_exists(tx_id)
            .await
    }

    pub async fn read_tx_from_db(
        &self,
        tx_id: &TransactionId,
    ) -> Result<Transaction, TransactionIndexError> {
        let r = self
            .tx_and_index_storage_with_cache
            .lock()
            .await
            .read_tx(tx_id)
            .await?;
        Ok(r)
    }

    pub async fn read_tx_index_node(
        &self,
        tx_id: &TransactionId,
    ) -> Result<TransactionIndexNode, TransactionIndexError> {
        let r = self
            .tx_and_index_storage_with_cache
            .lock()
            .await
            .read_tx_index_node(tx_id)
            .await?;
        Ok(r)
    }

    pub async fn read_block_height_of_tx(
        &self,
        tx_id: &TransactionId,
    ) -> Result<BlockHeight, TransactionIndexError> {
        let tx_ind = self.read_tx_index_node(tx_id).await?;

        let Some(blk_id) = tx_ind.get_belong_to_block() else {
            warn!("Not found block id in tx_index({})", tx_id);
            return Err(TransactionIndexError::NotFoundBlockIdInTxIndex(*tx_id));
        };

        let block_index = self.read_block_index_node(&blk_id).await?;
        let Some(block_index) = block_index else {
            warn!("Not found block index({})", blk_id);
            return Err(TransactionIndexError::NotFoundBlockIndexInDb(blk_id));
        };

        Ok(block_index.get_height())
    }

    pub async fn read_best_height(&self) -> Result<BlockHeight, TransactionIndexError> {
        let v = self
            .misc_item_storage_with_cache
            .lock()
            .await
            .read_best_height();
        Ok(v)
    }

    async fn read_block_index_node(
        &self,
        block_id: &BlockId,
    ) -> Result<Option<BlockIndexNode>, TransactionIndexError> {
        let r = self
            .block_and_index_storage_with_cache
            .lock()
            .await
            .read_block_index_node(block_id);
        Ok(r)
    }

    pub async fn dump_all_txs(&self) {
        let tx_and_index = self.tx_and_index_storage_with_cache.lock().await;
        // let mut index = 0;
        for (index, (tx_id, tx_ind)) in tx_and_index.iter_tx_index().enumerate() {
            println!("\n[No.{}] [id-{}], tx_ind: {:?}", index, tx_id, tx_ind);
            // index += 1;
        }
    }
}
